/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package travelingsalesman;

import java.util.logging.Logger;
import javax.swing.SwingWorker;

/**
 *
 * @author Rex Boris
 * @author DiaAWAY
 */
public class SelfOMapTSP extends SwingWorker {

    int N, M, t, numberOfCities, neighbourhoodC, neighbourhoodCDistance, end_check;
    double d_eta, treshold, totalDistance;
    double[][] input;
    double[] distance;
    double[][] weight, cities;
    private static final Logger log = Logger.getLogger(SelfOMapTSP.class.getName());
    /**
     * Determines whether the SOM loop should run or not. Used to indicate whether the threads should die or not
     */
    protected boolean isRunning = true;
//    /**
//     * Determines whether the SOM loop should pause
//     */
//    protected boolean isPausing = false;
    /**
     * How many iterations is needed for the SOM to inform listeners to an update.
     * The default is 3 iterations pr. update
     */
    protected int updateInterval = 1;

    private TSPGUI gui;

    public SelfOMapTSP(int dNeighbourhood, int M, int dNeighbour, int d_Eta, double[][] cities, TSPGUI gui) {
        this.neighbourhoodC = dNeighbour;
        this.d_eta = d_Eta;
        this.numberOfCities = cities.length;
        this.cities = cities;
        this.M = M; // neuron map size
        N = 2; // number of coordinates, x & y
        this.gui = gui;
        init();
        normalizeInput();
    }

    private void normalizeInput() {
        double maxX = Double.MIN_VALUE;
        double maxY = Double.MIN_VALUE;
        double minX = Double.MAX_VALUE;
        double minY = Double.MAX_VALUE;

        for (int i = 0; i < cities.length; i++) {
            // X VALUE
            if (cities[i][0] > maxX) {
                maxX = cities[i][0];
            }
            if (cities[i][0] < minX) {
                minX = cities[i][0];
            }
            // Y VALUE
            if (cities[i][1] > maxY) {
                maxY = cities[i][1];
            }
            if (cities[i][1] < minY) {
                minY = cities[i][1];
            }
        }

        // INPUT
        input = new double[numberOfCities][N];
        for (int i = 0; i < numberOfCities; i++) {
            input[i][1] = (cities[i][0] - minX) / (maxX - minX);
            input[i][0] = (cities[i][1] - minY) / (maxY - minY);
        }
    }

    private void init() {
        // WEIGHT, DISTANCE
        weight = new double[M][N];
        distance = new double[M];
        for (int j = 0; j < M; j++) {
            for (int i = 0; i < N; i++) {
                weight[j][i] = Math.random() / 10;
            }
        }
        t = 0;
        log.info("INIT");
    }

    public void distance_calc(int k) {
        for (int j = 0; j < M; j++) {
            distance[j] = 0.0;
            for (int i = 0; i < N; i++) {
                distance[j] += (input[k][i] - weight[j][i]) * (input[k][i] - weight[j][i]);
            }
        }
    }

    public int nodeSelection() {
        int j, k;
        for (j = 0, k = 0; k < M; k++) {
            if (distance[j] > distance[k]) {
                j = k;
            }
        }
        return j;
    }

    public void calculate_weights(int j, int k) {
        int i, l, m;
        double eta;
        if (d_eta > 0) {
            eta = 1.0 / (double) (t + 2);
        } else {
            eta = 1.0 / Math.log((double) (t + 3));
        }

        for (m = j - neighbourhoodC; m <= (j + neighbourhoodC); m++) {
            l = m;
            if (l < 0) {
                l += M;
            } else if (l >= M) {
                l -= M;
            }

            for (i = 0; i < N; i++) {
                weight[l][i] = weight[l][i] + eta * (input[k][i] - weight[l][i]);
            }
        }
    }

    public double Ring_distance() {
        double dist = 0.0;
        for (int i = 0; i < M - 1; i++) {
            dist += Math.sqrt((weight[i][0] - weight[i + 1][0]) * (weight[i][0] - weight[i + 1][0])
                    + (weight[i][1] - weight[i + 1][1]) * (weight[i][1] - weight[i + 1][1]));
        }
        dist += Math.sqrt((weight[M - 1][0] - weight[0][0]) * (weight[M - 1][0] - weight[0][0])
                + (weight[M - 1][1] - weight[0][1]) * (weight[M - 1][1] - weight[0][1]));
        return dist;
    }

    @Override
    protected double[][] doInBackground() throws Exception {
        log.entering(SelfOMapTSP.class.getName(), "run");
        int i = 0, j = 0, k = 0, l = 0, m = 0;
        treshold = 0.001;
        double old_weights[] = new double[N];

        int iterations = 0;
        int numUpdates = 0;
        while (isRunning) {
            if (i >= neighbourhoodCDistance && neighbourhoodC > 0) {
                neighbourhoodC--;
                i = 0;
            }
            if (d_eta > 0 && m >= d_eta) {
                t++;
                m = 0;
            }
            if (d_eta < 0) {
                t++;
            }
            if (k >= numberOfCities) {
                k -= numberOfCities;
            }

            distance_calc(k);	// Step 2 & 3
            j = nodeSelection();	// Step 4
            old_weights[0] = weight[j][0];
            old_weights[1] = weight[j][1];
            calculate_weights(j, k);	// Step 5

            if (Math.abs(old_weights[0] - weight[j][0]) < treshold
                    && Math.abs(old_weights[1] - weight[j][1]) < treshold) {
                end_check++;	// check nodes' change
            } else {
                end_check = 0;
            }
            if (end_check >= numberOfCities) {
                isRunning = false; // loop stop
                totalDistance = Ring_distance();
            }
            i++;
            k++;
            l++;
            m++;
            iterations++;
            if (iterations == updateInterval) {
                gui.addToGraph(weight);
                iterations = 0;
                numUpdates++;
            }
        }
        // final info
        gui.addToGraph(weight);
        gui.updateDistance(totalDistance);
        gui.updateMaxCount(numUpdates);
        log.exiting(SelfOMapTSP.class.getName(), "run");
        return weight;
    }
}
